// winmain.cpp

#ifndef _CRT_SECURE_NO_DEPRECATE
#define _CRT_SECURE_NO_DEPRECATE 1
#endif

#include <windows.h>
#include <d3d9.h>
#include <d3dx9tex.h>
#include <Dxerr.h>  
#include <dsound.h>

#include <stdio.h>
#include <math.h>

#include "dsutil.h"
#include "grid.h"

HINSTANCE hInst;				// holds the instance for this app
HWND wndHandle;					// global window handle

LPDIRECT3D9             pD3D;
LPDIRECT3DDEVICE9       pd3dDevice;

// bouncing ball
LPD3DXMESH pBallMesh;
float BallRadius = 5;
D3DXVECTOR3 BallVel(0, 0, 0);
D3DXVECTOR3 BallPos(0, BallRadius, 0);

// camera variables
D3DXMATRIX matView;					// the view matrix
D3DXMATRIX matProj;					// the projection matrix
D3DXVECTOR3 camPos;			// the position of the camera
D3DXVECTOR3 camLook;				// where the camera is pointing
bool Wireframe = false;

LPDIRECT3DTEXTURE9 groundTexture = NULL;
LPDIRECT3DTEXTURE9 ballTexture = NULL;

Grid                    *pGrid;

// DirectSound functions and variables
LPDIRECTSOUND8        pDS;	// The DirectSound Device

bool ballHit = false;

bool initDirectSound(HWND hwnd);
void shutdownDirectSound(void);
LPDIRECTSOUNDBUFFER LoadWaveToSoundBuffer(char wavFilename[]);
void playSound(LPDIRECTSOUNDBUFFER whichBuffer);
void playSoundLoop(LPDIRECTSOUNDBUFFER whichBuffer);
void stopSound(LPDIRECTSOUNDBUFFER whichBuffer);



////////////////////////////////////////////// forward declarations
bool    initWindow(HINSTANCE hInstance);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

bool initDirect3D(HWND hwnd);
void shutdownDirect3D(void);

void createCamera(float nearClip, float farClip);
void moveCamera(D3DXVECTOR3 vec);
void pointCamera(D3DXVECTOR3 vec);

#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480

float Abs(float x) {
	if (x >= 0) return x;
	else return -x;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	HRESULT hr;
	D3DXMATRIX translate, id;
	D3DXVECTOR3 gridPt;

	int curVolume = 0;
	int volChange = 1000;
	LPDIRECTSOUNDBUFFER DSBuffer;
	
	
	// call our function to init and create our window
	if (!initWindow(hInstance))
	{
		MessageBox(NULL, "Unable to create window", "ERROR", MB_OK);
		return false;
	}

	if (!initDirect3D(wndHandle))
	{
		MessageBox(NULL, "Unable to init Direct3D", "ERROR", MB_OK);
		return false;
	}

	// initialize DirectSound
	if (!initDirectSound(wndHandle))
	{
		MessageBox(NULL, "Unable to init DirectSound", "ERROR", MB_OK);
		return false;
	}

	// load a wave file into a directsound buffer
	DSBuffer = LoadWaveToSoundBuffer("sound1.wav");
	

	if (!DSBuffer)
		return false;

	camPos = D3DXVECTOR3(0.0f, 5.0f, -40.0f);
	camLook = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
	
	D3DXVECTOR3 v0(-15.0f, 0.0f, -15.0f);
	D3DXVECTOR3 v1( 15.0f, 0.0f, -15.0f);
	D3DXVECTOR3 v2( 15.0f, 0.0f,  15.0f);
	D3DXVECTOR3 v3(-15.0f, 0.0f,  15.0f);

	D3DXCreateTextureFromFile(pd3dDevice, "borderedTile.png", &groundTexture);
	D3DXCreateTextureFromFile(pd3dDevice, "ball.png", &ballTexture);

	pGrid = new Grid(11, 11, &v0, &v1, &v2, &v3);
	pGrid->CreateDirectXMesh(pd3dDevice);

	D3DXCreateSphere(pd3dDevice, 2, 20, 20, &pBallMesh, NULL);


	// Main message loop:
	// Enter the message loop
	MSG msg; 
	ZeroMemory( &msg, sizeof(msg) );
	while( msg.message!=WM_QUIT )
	{
		// check for messages
		if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
		{
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}
		// this is called when no messages are pending
		else
		{
			createCamera(0.1f, 1000.0f);		// near clip plane, far clip plane
			moveCamera(camPos);
			pointCamera(camLook);

			float speed, maxSpeed = 0.5;
			long volume;

			// update the ball velocity and position

			// fake some gravity here by increasing the ball's negative y velocity
			BallVel.y -= .01f;

			// limit the ball's speed to some maxSpeed value
			if (Abs(D3DXVec3Length(&BallVel)) > maxSpeed) {
				D3DXVec3Normalize(&BallVel, &BallVel);
				D3DXVec3Scale(&BallVel, &BallVel, maxSpeed);
			}   

			// update the ball's position
			BallPos = BallPos + BallVel;

			// rebound if ball hits the grid
			pGrid->GetGridPoint(&gridPt, BallPos.x, BallPos.z);
			if (BallPos.y < gridPt.y) {
				// adjust the volume based on the speed of the collision
				speed = D3DXVec3Length(&BallVel);
				volume = long(DSBVOLUME_MAX - (50.0f * BallPos.z));

				// if speed is really slow set volume to min value
			   /* if (speed < maxSpeed * 0.1f) {
					volume = DSBVOLUME_MIN;
				}*/

				hr = DSBuffer->SetVolume(volume);

				DSBuffer->SetPan(long(DSBPAN_CENTER + (200 * BallPos.x)));

				if( FAILED( hr  ) )    
					DXTRACE_ERR( TEXT("Volume error "), hr );

				// play this sound
				if( (BallVel.y > 0.05f) || (BallVel.y < -0.05f) )
				{
					ballHit = true;
				}

				if(ballHit)
				{
					playSound(DSBuffer);
					ballHit = false;
				}

				// reflect the ball's velocity in the y direction
				// make the rebound have some loss of energy
				BallVel.y *= -.8f;

				// adjust ball slightly above the grid
				BallPos.y = gridPt.y + .01f;

			}

			D3DXMatrixTranslation(&translate, BallPos.x, BallPos.y, BallPos.z);
			D3DXMatrixIdentity(&id);

			// Clear the backbuffer to a black color
			pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, 
							  D3DCOLOR_XRGB(5, 5, 5), 1.0f, 0 );

			pd3dDevice->BeginScene();

			pd3dDevice->SetTexture(0, groundTexture);		// Apply ground texture to grid

			pd3dDevice->SetTransform(D3DTS_WORLD, &id);
			pGrid->DrawDirectXMesh(pd3dDevice);

			pd3dDevice->SetTexture(0, ballTexture);		// Apply texture to ball

			pd3dDevice->SetTransform(D3DTS_WORLD, &translate);
			pBallMesh->DrawSubset(0);

			pd3dDevice->EndScene();

			// keep ball on grid
			if (BallPos.x < -20.0f) { BallPos.x = -20.0f; BallVel.x =   0.0f; }
			if (BallPos.x >  20.0f) { BallPos.x =  20.0f; BallVel.x =   0.0f; }
			if (BallPos.z <  -5.0f) { BallPos.z =  -5.0f; BallVel.z =   0.0f; }
			if (BallPos.z >  35.0f) { BallPos.z =  35.0f; BallVel.z =   0.0f; }

			// Present the backbuffer contents to the display
			pd3dDevice->Present( NULL, NULL, NULL, NULL );
		}
	}

	// release and shutdown Direct3D
	shutdownDirect3D();

	// Release the DirectSound buffer created above
	if (DSBuffer)
	{
		DSBuffer->Release();
		DSBuffer = NULL;
	}

	// shutdown DirectSound
	shutdownDirectSound();

	return (int) msg.wParam;
}

/*******************************************************************
* shutdownDirectSound
* Releases the DirecSound device
*******************************************************************/
void shutdownDirectSound(void)
{
	if (pDS)
	{
		pDS->Release();
		pDS = NULL;
	}
}

/*******************************************************************
* initDirectSound
* Initializes DirectSound
*******************************************************************/
bool initDirectSound(HWND hwnd)
{
	HRESULT hr;

	hr = DirectSoundCreate8( NULL, &pDS, NULL );
	if FAILED (hr)
		return false;

	// Set DirectSound cooperative level 
	hr = pDS->SetCooperativeLevel( hwnd, DSSCL_PRIORITY );
	if FAILED ( hr )
		return false;

	return true;
}

/*******************************************************************
* LoadWaveToSoundBuffer
* Loads a wave file into a DirectSound Buffer
*******************************************************************/
LPDIRECTSOUNDBUFFER LoadWaveToSoundBuffer(char wavFilename[])
{
	LPDIRECTSOUNDBUFFER apDSBuffer = NULL;
	CWaveFile *wavFile;
	HRESULT hr;

	wavFile = new CWaveFile();
	wavFile->Open(wavFilename, NULL, WAVEFILE_READ );
	if( wavFile->GetSize() == 0 )
	{
		MessageBox(wndHandle, "invalid file", "ERROR", MB_OK);
		return false;
	}

	// set up the buffer descriptor
	DSBUFFERDESC dsbd;
	ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) );
	dsbd.dwSize          = sizeof(DSBUFFERDESC);
	// this part give us volume capability
	// you can add additional flags for other caps like panning
	dsbd.dwFlags         = DSBCAPS_CTRLVOLUME;
	dsbd.dwBufferBytes   = wavFile->GetSize();
	dsbd.guid3DAlgorithm = GUID_NULL;
	dsbd.lpwfxFormat     = wavFile->m_pwfx;

	hr = pDS->CreateSoundBuffer( &dsbd, &apDSBuffer, NULL );
	if FAILED (hr)
	{
		MessageBox(NULL, "unable to create sound buffer", "ERROR", MB_OK);
		return NULL;
	}

	VOID*   pDSLockedBuffer      = NULL; // Pointer to locked buffer memory
	DWORD   dwDSLockedBufferSize = 0;    // Size of the locked DirectSound buffer
	DWORD   dwWavDataRead        = 0;    // Amount of data read from the wav file 
	
	hr = apDSBuffer->Lock( 0, wavFile->GetSize(), 
						   &pDSLockedBuffer, &dwDSLockedBufferSize, 
						   NULL, NULL, 0L );
	if FAILED (hr)
		return NULL;

	// Reset the wave file to the beginning 
	wavFile->ResetFile();

	// Read the wave file
	hr = wavFile->Read( (BYTE*) pDSLockedBuffer,
					   dwDSLockedBufferSize, 
					   &dwWavDataRead );
	// Check to make sure that this was successful
	if FAILED (hr)
		return NULL;

	// Check to make sure the wav file is not empty
	if( dwWavDataRead == 0 )
	{
		// Wav is blank, so just fill with silence
		FillMemory( (BYTE*) pDSLockedBuffer, 
					dwDSLockedBufferSize, 
					(BYTE)(wavFile->m_pwfx->wBitsPerSample == 8 ? 128 : 0 ) );
	}
	else if( dwWavDataRead < dwDSLockedBufferSize )
	{
		// Don't repeat the wav file, just fill in silence 
		FillMemory( (BYTE*) pDSLockedBuffer + dwWavDataRead, 
				   dwDSLockedBufferSize - dwWavDataRead, 
				   (BYTE)(wavFile->m_pwfx->wBitsPerSample == 8 ? 128 : 0 ) );
	}

	// Unlock the buffer, we don't need it anymore.
	apDSBuffer->Unlock( pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0 );

	// Clean up
	delete wavFile;

	return apDSBuffer;
}

/*******************************************************************
* playSound
* plays a sound currently in a buffer only once
*******************************************************************/
void playSound(LPDIRECTSOUNDBUFFER whichBuffer)
{
	whichBuffer->Play( 0, 0, 0);
}

/*******************************************************************
* playSoundLoop
* plays a sound in a buffer repeatedly
*******************************************************************/
void playSoundLoop(LPDIRECTSOUNDBUFFER whichBuffer)
{
	whichBuffer->Play( 0, 0, DSBPLAY_LOOPING );
}

/*******************************************************************
* stopSound
* stops the sound in this buffer from playing
*******************************************************************/
void stopSound(LPDIRECTSOUNDBUFFER whichBuffer)
{
	whichBuffer->Stop();
}

bool initWindow(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= (WNDPROC)WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= 0;
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= NULL;
	wcex.lpszClassName	= "DirectX Lab 6(Space bar to bounce ball - Wk 6 Ronald Ram";
	wcex.hIconSm		= 0;
	RegisterClassEx(&wcex);

	wndHandle = CreateWindow("DirectX Lab 6(Space bar to bounce ball - Wk 6 Ronald Ram", 
							 "DirectX Lab 6(Space bar to bounce ball - Wk 6 Ronald Ram", 
							 WS_OVERLAPPEDWINDOW,
							 CW_USEDEFAULT, 
							 CW_USEDEFAULT, 
							 SCREEN_WIDTH, 
							 SCREEN_HEIGHT, 
							 NULL, 
							 NULL, 
							 hInstance, 
							 NULL);
   if (!wndHandle)
	  return false;
   
   ShowWindow(wndHandle, SW_SHOW);
   UpdateWindow(wndHandle);

   return true;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) 
	{
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
	case WM_KEYDOWN:
		int fwKeys;
		LPARAM keyData;
		fwKeys = (int)wParam;    // virtual-key code 
		keyData = lParam;          // key data 

		switch(fwKeys)
		{
		case VK_UP:
			{
				BallVel.z += 0.3f;
			}
			break;
		case VK_DOWN:
			{
				BallVel.z -= 0.3f;
			}
			break;
		case VK_LEFT:
			{
				BallVel.x -= 0.3f;
			}

			break;
		case VK_RIGHT:
			{
				BallVel.x += 0.3f;
			}
			break;
		case VK_SPACE:
			{
				BallVel.y += 0.3f;
			}
			break;
		case 'W':
					   
			break;
		case VK_ESCAPE:
			PostQuitMessage(0);
			break;
		default:
			break;
		}

		break;

	default:
		break;
	}
	return DefWindowProc(hWnd, message, wParam, lParam);
}

bool initDirect3D(HWND hwnd)
{
	if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
		return false;
	
	D3DPRESENT_PARAMETERS d3dpp; 
	ZeroMemory( &d3dpp, sizeof(d3dpp) );
	d3dpp.Windowed = TRUE;
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
	d3dpp.BackBufferCount  = 1;
	d3dpp.BackBufferHeight = SCREEN_HEIGHT;
	d3dpp.BackBufferWidth  = SCREEN_WIDTH;
	d3dpp.hDeviceWindow    = hwnd;
	
	if( FAILED( pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
									  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
									  &d3dpp, &pd3dDevice ) ) )
	return false;

	if (Wireframe) {
		pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
	} else {
		pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
	}
	pd3dDevice->SetRenderState(D3DRS_LIGHTING, false);
	
	return true;
}

void shutdownDirect3D(void)
{
	if( pd3dDevice != NULL) 
	{
		pd3dDevice->Release();
		pd3dDevice = NULL;
	}
	if( pD3D != NULL)
	{
		pD3D->Release();
		pD3D = NULL;
	}
}

/*************************************************************************
* createCamera
* creates a virtual camera
*************************************************************************/
void createCamera(float nearClip, float farClip)
{
	//Here we specify the field of view, aspect ration and near and far clipping planes.
	D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI/4, float(640)/480, nearClip, farClip);
	pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);

}

/*************************************************************************
* moveCamera
* moves the camera to a position specified by the vector passed as a 
* parameter
*************************************************************************/
void moveCamera(D3DXVECTOR3 vec)
{
	camPos = vec;
}

/*************************************************************************
* pointCamera
* points the camera a location specified by the passed vector
*************************************************************************/
void pointCamera(D3DXVECTOR3 vec)
{
	camLook = vec;

	D3DXMatrixLookAtLH(&matView, &camPos,		//Camera Position
								 &camLook,		//Look At Position
								 &D3DXVECTOR3(0.0f, 1.0f, 0.0f));		//Up Direction

	pd3dDevice->SetTransform(D3DTS_VIEW, &matView);
}